package com.oros.db;

import java.util.*;


import com.vaadin.data.*;
import com.vaadin.data.util.filter.UnsupportedFilterException;
import com.vditl.core.*;

public class AETableContainer implements Container, Container.Indexed,
       Container.ItemSetChangeNotifier,Container.Ordered,Container.Filterable,
       Container.Sortable {
	private final static long serialVersionUID = -132324432590L;
	public String tableName,tableName_Alias;
	public int columnAmount,idColumn,parentColumn=-1;
	public String[] columnNames;
	public Object[] columnDefaults;
	public Class<?>[] columnTypes;
	public boolean opened=true;
	protected ArrayList<String> columnNameCollection,sortedColumnNameCollection;
	protected UniVector filters = new UniVector();
	protected LinkedList<Container.ItemSetChangeListener> itemSetChangeListeners;
	protected boolean modified=false,restrictEventFire=false;
	protected boolean restrictEventFireSuspended = false;
    protected String primaryKey;
    protected boolean isPrimaryKeyLong=false;
	AEGeneratedData[] generatedData;
    protected 
      //transient 
      UniVector source;
    protected 
      //transient 
      boolean sourceReady = false;
    protected UniVector delsource = new UniVector(),editSource=new UniVector();
    protected UniVector deleteConstraints = new UniVector();
    protected LinkedHashMap<String,Integer> colNameMap= new 
    		LinkedHashMap<String,Integer>();
    protected int AppEngineRowOffset=0,AppEngineRowLimit=10000;
    protected ArrayList<AEUniqueIndexPool> uniqueIndexes=new ArrayList<AEUniqueIndexPool>();
    protected VaadEngine engine;
    public int[] sortedColumns;
    public int sortedColumnAmount=0;
    protected ArrayList<AEFilter> aeFilter = new ArrayList<AEFilter>();
    protected ArrayList<AESort> aeSort = new ArrayList<AESort>();
    protected ArrayList<AEValidator> validators = new ArrayList<AEValidator>();
    protected AETableContainer commitDependant = null;
    protected boolean generalParentView = false;  
    protected long internalGeneratedId=0;
    protected AEMultiEqualFilter multiEqualFilter;
    protected UniVector multiEqualMap = new UniVector(); 
    final public static int SHOW_INFO_INTERNAL=4;
    final public static int SHOW_INFO_FULL_ROW=1;
    final public static int SHOW_INFO_FILTERED=2;
    final public static int SHOW_INFO_WITH_TYPES=8;
    final public static int SHOW_INFO_FILTERS=16;
    final public static int SHOW_INFO_SUBTABLES=32;
    final public static int SHOW_INFO_ALL=SHOW_INFO_INTERNAL | SHOW_INFO_FULL_ROW |
    		SHOW_INFO_FILTERED | SHOW_INFO_FILTERS | SHOW_INFO_SUBTABLES;
 	@SuppressWarnings("unchecked")
	public AETableContainer(String tableName,String[] columnNames,
			   Object[] oColumnDefaults,Class<?>[] columnTypes,boolean refreshImmediately,
			   String primaryKeyColumn,String parentColumnName,VaadEngine engine) throws Exception
	{
		this.engine = engine;
		this.tableName = tableName;
		if (tableName==null || tableName.length()==0) throw new Exception("Empty table name");
		int n = tableName.length();
		for(int i=0;i<n;i++)
		{
           char c=tableName.charAt(i);
           if (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z'))) continue;
           if ((c>='0')&&(c<='9')) 
           {
        	   if (i==0) throw new Exception("Table name cannot start with a digit: "+tableName);
        	   continue;
           }
           throw new Exception("Bad characters in table name \""+tableName+"\" at position "+i);
		}
		tableName_Alias = tableName;
		primaryKey = primaryKeyColumn;
		this.columnNames = columnNames;
		if (columnNames==null) throw new Exception("Column name array is null in table "+tableName);
		columnAmount = columnNames.length;
		if (columnAmount==0)
		{
			throw new Exception("Column name array in table "+tableName+" is of zero length");
		}
		sortedColumns = new int[columnAmount];
		if (oColumnDefaults==null) throw new Exception("Column default array is null in table "+tableName);
		this.columnDefaults = oColumnDefaults.clone();
		if (columnDefaults.length!=columnAmount) throw new Exception("Column default array in table "+tableName+" has the length of "+columnDefaults.length+
				" but this length must be "+columnAmount);
		this.columnTypes = columnTypes;
		if (columnTypes==null) throw new Exception("Column type array is null in table "+tableName);
		if (columnTypes.length!=columnAmount) throw new Exception("Column type array in table "+tableName+" has the length of "+columnTypes.length+
				" but this length must be "+columnAmount);
		idColumn=-1;parentColumn=-1;
		if (primaryKey==null) throw new Exception("Primary key column name is null in table "+tableName);
		columnNameCollection=new ArrayList<String>();
		for(int i=0;i<columnAmount;i++)
		{
			if (columnTypes[i]==null) throw new Exception("Type of column "+i+" in table "+tableName+" is null");
			if (columnNames[i].equals(primaryKey)) {idColumn=i;}
			if (parentColumnName!=null && columnNames[i].equals(parentColumnName))
				parentColumn=i;
			columnNameCollection.add(columnNames[i]);
			colNameMap.put(columnNames[i], new Integer(i));
		}
        if (idColumn<0) throw new Exception("Invalid primary key column name: "+primaryKey+" in table "+tableName);
        Class<?> idtype = columnTypes[idColumn];
        if (idtype==Long.class) isPrimaryKeyLong=true;
        else if (idtype!=Integer.class) throw new Exception("Type "+idtype.getCanonicalName()+" is not allowed for primary key (only Integer or Long is allowed): table "+tableName);
		if (parentColumn>=0) setTableParentNewValue(null,false);
		sortedColumnNameCollection =(ArrayList<String>) columnNameCollection.clone();
		if (refreshImmediately) refresh();
	}
 	public AETableContainer(String tableName,String[] columnNames,
			   Object[] columnDefaults,Class<?>[] columnTypes,boolean refreshImmediately,
			   String primaryKeyColumn,VaadEngine engine) throws Exception
	{
 		this(tableName, columnNames, columnDefaults, columnTypes, refreshImmediately, primaryKeyColumn, null,engine);
	}
 	public AETableContainer(AETableDefinition definition,boolean refreshImmediately,VaadEngine engine) throws Exception
 	{
 		this(definition.tableName,definition.columnNames,definition.columnDefaults,
 				definition.columnTypes,false,definition.primaryKeyColumn,
 				definition.parentColumn,engine);
 		generatedData =definition.generatedData;
 		if (refreshImmediately) refresh();
 	}
 	protected void makeSourceReady()
 	{
 		sourceReady = false;
 		try
 		{
 		  refresh();
 		}
 		catch(Exception e)
 		{
 			e.printStackTrace();
 		}
 	}
 	public boolean isCommitted()
 	{
 		return !modified;
 	}
 	public void removeAllUniqueIndexes()
 	{
 		uniqueIndexes.clear();
 	}
 	public void addUniqueIndex(String[] columns) 
 	{
 		if (columns==null || columns.length==0)
 			throw new RuntimeException("AETableContainer.addUniqueIndex columns are empty");
 		int n=columns.length;
 		int[] cols = new int[n];
 		for(int i=0;i<n;i++)
 		{
 			int r = this.getColumnIndex(columns[i]);
 			if (r<0) throw new RuntimeException("AETableContainer.addUniqueIndex column name "+columns[i]+" is invalid");
            cols[i]=r; 			
 		}
 		uniqueIndexes.add(new AEUniqueIndexPool(cols));
 	}
    public void addGeneratedColumns(AEGeneratedColumn[] generatedColumns)
    {
   	 if (generatedColumns==null || generatedColumns.length==0) return;
   	 int n= generatedColumns.length;
   	 int oldColAmnt = columnNames.length;
   	 int newColAmnt = oldColAmnt + n;
   	 int o = generatedData==null?0:generatedData.length;
   	 String[] tmpColumnNames = new String[newColAmnt];
   	 Object[] tmpColumnDefaults = new Object[newColAmnt];
   	 Class<?>[] tmpColumnTypes = new Class<?>[newColAmnt];
   	 AEGeneratedData[] tmpGeneratedData = new AEGeneratedData[o+n];
   	 for(int i=0;i<oldColAmnt;i++)
   	 {
   		 tmpColumnNames[i]=columnNames[i];
   		 tmpColumnDefaults[i]=columnDefaults[i];
   		 tmpColumnTypes[i]=columnTypes[i];
   	 }
   	 for(int i=0;i<o;i++)
   	 {
   		 tmpGeneratedData[i]=generatedData[i];
   	 }
   	 for(int i=0;i<n;i++)
   	 {
   		 String colName = generatedColumns[i].columnName;
   		 tmpColumnNames[oldColAmnt+i]=colName;
   		 columnNameCollection.add(colName);
   		 sortedColumnNameCollection.add(colName);
   		 tmpColumnDefaults[oldColAmnt+i]=generatedColumns[i].defaultValue;
   		 tmpColumnTypes[oldColAmnt+i]=generatedColumns[i].type;
   		 colNameMap.put(generatedColumns[i].columnName, new Integer(oldColAmnt+i));
   		 tmpGeneratedData[i+o]=generatedColumns[i].generator;
   	 }
   	 columnNames = tmpColumnNames;
   	 columnDefaults = tmpColumnDefaults;
   	 columnTypes = tmpColumnTypes;
   	 generatedData = tmpGeneratedData;
   	 this.columnAmount+=n;
   	 if (sourceReady)
   	 {
   	   	 int k= source.size();
   	   	 for(int i=0;i<k;i++)
   	   	 {
   	   		 AERawItem item=(AERawItem)source.get(i);
   	   		 item.updateGeneratedDataColumns();
   	   	 }
   	 }
   	 else
   	 {
   	   	 int k= delsource.size();
   	   	 for(int i=0;i<k;i++)
   	   	 {
   	   		 AERawItem item=(AERawItem)delsource.get(i);
   	   		 item.updateGeneratedDataColumns();
   	   	 }
   	   	 k= editSource.size();
   	   	 for(int i=0;i<k;i++)
   	   	 {
   	   		 AERawItem item=(AERawItem)editSource.get(i);
   	   		 item.updateGeneratedDataColumns();
   	   	 }
   	 }
    }
    public boolean addGeneratedDataChecked(AEGeneratedColumn[] generatedColumns)
    {
    	int generatedColumnsAmount=generatedColumns.length;
    	for(int i=0;i<generatedColumnsAmount;i++)
    	{
    		String colname=generatedColumns[i].columnName;
    		if (colNameMap.get(colname)!=null) return false;
    	}
    	addGeneratedColumns(generatedColumns);
    	return true;
    }
    public boolean addGeneratedDataStringFormatter(String newColumnName,AEFieldFormatter formatter)
    {
    	AEGeneratedColumn[] cols=new AEGeneratedColumn[1];
    	cols[0] = new AEGeneratedColumn(newColumnName,String.class,"",formatter);
    	return addGeneratedDataChecked(cols);
    }
    public boolean addStringGeneratedDataForColumn(String generatedColumnName,AEGeneratedData generator)
    {
    	if (colNameMap.get(generatedColumnName)!=null) return false;
    	AEGeneratedColumn generatedColumn=new AEGeneratedColumn(generatedColumnName,String.class,"",generator);
    	AEGeneratedColumn[] generatedColumns=new AEGeneratedColumn[1];
    	generatedColumns[0]=generatedColumn;
    	addGeneratedColumns(generatedColumns);
    	return true;
    }
    public boolean addStringListGeneratedDataForColumn(String baseColumnName,String generatedColumnName,LinkedHashMap<?,?> map)
    {
    	Integer colIndex = colNameMap.get(baseColumnName);
    	if (colIndex==null) return false;
    	AEGeneratedData generator = new AEGeneratedListData(baseColumnName,map);
    	return addStringGeneratedDataForColumn(generatedColumnName,generator);
    }
    public boolean addStringListGeneratedDataForColumn(String baseColumnName,String generatedColumnName,LinkedHashMap<?,?> map,AETranslation translation)
    {
    	Integer colIndex = colNameMap.get(baseColumnName);
    	if (colIndex==null) return false;
    	AEGeneratedData generator = new AEGeneratedListData(baseColumnName,map,translation);
    	return addStringGeneratedDataForColumn(generatedColumnName,generator);
    }
    public boolean addPasswordGeneratedDataForColumn(String passwordColumn,String passwordConfirmColumn)
    {
    	int n=1;
    	if (colNameMap.get(passwordColumn)!=null) return false;
    	if (passwordConfirmColumn!=null)
    	{
    		n=2;
    		if (colNameMap.get(passwordConfirmColumn)!=null) return false;
    	}
    	AEGeneratedColumn[] generatedColumns=new AEGeneratedColumn[n];
    	for(int i=0;i<n;i++)
    	{
    		String colName=i==0?passwordColumn:passwordConfirmColumn;
    		generatedColumns[i]=new AEGeneratedColumn(colName,String.class,"",new AEGeneratedWritable(""));
    	}
    	addGeneratedColumns(generatedColumns);
    	return true;
    }
 	private void validateUniqueIndexes() throws AEUniqueIndexViolationException
 	{
 		int n=uniqueIndexes.size();
 		if (!sourceReady) makeSourceReady();
		int m = source.size();
 		for(int i=0;i<n;i++)
 		{
 			//this.showSourceDetailedInfo(SHOW_INFO_FULL_ROW);
 			AEUniqueIndexPool uipool = uniqueIndexes.get(i);
 			uipool.clear();
 			for(int j=0;j<m;j++)
 			{
 				AERawItem item=(AERawItem)source.get(j);
 				if (!uipool.add(item))
 				{
 					String info = uipool.getItemUniqueInfo(item);
 					throw new AEUniqueIndexViolationException("Table "+this.tableName+" unique index violation: "+info+" and "+uipool.get(item).toString());
 				}
 			}
 		}
 	}
 	public void validate() throws Exception
 	{
 		validateUniqueIndexes();
 		validateDeleteConstraints();
 		if (this.commitDependant!=null)
 			this.commitDependant.validate();
 	}
 	public void setModified(boolean modified)
	{
		this.modified = modified;
		if (modified && !this.restrictEventFire)
		 { 
			fireContentsChange();
			//System.out.println("AETableContainer.setModified called");
		 }
	}
	public static boolean isZeroId(Object id)
	{
	   return (id==null || (id instanceof Integer && ((Integer)id).intValue()==0 ) ||
			   (id instanceof Long && ((Long)id).longValue()==0L ) || 
			   (id instanceof AERawItem && ((AERawItem)id).getIdKey()==0) );
	}
	public int getPrimaryColumnNo()
	{
		return idColumn;
	}
	protected AERawItem addCustomItem(Object id,boolean isNew) throws Exception
	{
 		if (!sourceReady) makeSourceReady();
		columnDefaults[idColumn]=id;
        AERawItem item = new AERawItem(this,columnNames,columnDefaults,columnTypes,isNew,primaryKey,generatedData);
    	if (isNew && isZeroId(id))
    		{
    		   engine.allocateNewId(item,engine.isBaseTableSeparationSupported()?tableName_Alias:tableName);
    		   if (item.getIdKey()==0) item.setPrimaryKey(--internalGeneratedId);
    		   if (source.indexOf(item)>=0) return null; 
    		}
        source.add(item);
        editSource.add(item);
		return item;
	}
	public Item addNewItem(Object[] data)
	{
		if (!sourceReady) makeSourceReady();
		Object id = addItem();
		if (id==null) return null;
		AERawItem item =(AERawItem)source.get(id);
		int n=columnNames.length;
		if (item==null) return null;
		for(int i=0;i<n;i++)
			if (i!=idColumn && i!=parentColumn)
		{
		    item.setPropertyOfItem(i, data[i]);
		}
		return item;
	}
	public Item addNewItem(String[] cols,Object[] data)
	{
		if (!sourceReady) makeSourceReady();
		Object id = addItem();
		if (id==null) return null;
		AERawItem item =(AERawItem)source.get(id);
		int n=cols.length;
		if (item==null) return null;
		for(int k=0;k<n;k++)
		{
			int i = this.getColumnIndex(cols[k]);
			if (i<0) throw new RuntimeException("Column "+cols[k]+" does not present in table "+tableName);
			if (i!=idColumn && i!=parentColumn)
		    {
			  item.setPropertyOfItem(i, data[k]);	
		    }
		}
		return item;
	}
	
	@Override
	public Item addItem(Object itemId) throws UnsupportedOperationException 
	{
		if (parentColumn>=0 && this.generalParentView)
            throw new UnsupportedOperationException("Column parent must be set before");
		if (!sourceReady) makeSourceReady();
        long lvalue=-1;
		if (itemId instanceof AERawItem)
		{
			lvalue=((AERawItem)itemId).getIdKey();
		} else if (itemId instanceof String)
		{
			lvalue = (Long.parseLong((String)itemId));
		} else if (itemId instanceof Integer) lvalue=((Integer)itemId).longValue();
		else if (itemId instanceof Long) lvalue = ((Long)itemId).longValue();
        if (lvalue<=0)
        	throw new UnsupportedOperationException("Primary key "+itemId+" is not supported");
		
		if (isPrimaryKeyLong)
			{
			itemId = new Long(lvalue); 
			}
		else
		{
			itemId = new Integer((int)lvalue);
		}
        if (source.indexOf(itemId)>=0) return null;
        AERawItem item=null;
        try
        {
          item=addCustomItem(itemId,true);
          fillRawItemWithEqualContainerFilterValues(item);
        }
        catch(Exception e)
        {
        	e.printStackTrace();
        	return null;
        }
    	setModified(true);
    	return item;
	}
    @Override
    public Object addItem() throws UnsupportedOperationException {
		if (parentColumn>=0 && this.generalParentView)
                 throw new UnsupportedOperationException("Column parent must be set before");
		if (!sourceReady) makeSourceReady();
    	Object id = null;
		if (isPrimaryKeyLong)
			id = new Long(0);
		else
		id = new Integer(0);
		AERawItem item=null;
		try
    	{
	    	item=addCustomItem(id,true);
	    	fillRawItemWithEqualContainerFilterValues(item);
    	}
    	catch(Exception e)
    	{
    		e.printStackTrace();
    		return null;
    	}
    	setModified(true);
    	return item.getPrimaryColumnValue();
    }
    @Override
	public Collection<?> getContainerPropertyIds() {
		return columnNameCollection; 
	}

	@Override
	public Property getContainerProperty(Object itemId, Object propertyId) {
		if (!sourceReady) makeSourceReady();
        AERawItem item=(AERawItem)source.get(itemId);
        if (item==null) return null;
		return item.getItemProperty(propertyId);
	}

	@Override
	public Class<?> getType(Object propertyId) {
		int i=colNameMap.get(propertyId);
		return columnTypes[i];
	}

	@Override
	public boolean addContainerProperty(Object propertyId, Class<?> type,
			Object defaultValue) throws UnsupportedOperationException {
		
		throw new UnsupportedOperationException("Cannot add property to AETableContainer");
	}
    public void registerItemProperties(Item item)
    {
		if (!sourceReady) makeSourceReady();
    	AERawItem theItem = (AERawItem)item;
    	int nf = filters.size();
    	boolean bFilterPassed = true;
    	for(int i=0;(i<nf)&& bFilterPassed;i++)
    	{
    		Filter f = (Filter)filters.get(i);
    		if (!f.passesFilter(theItem, theItem)) bFilterPassed = false;
    	}
    	theItem.setFiltered(bFilterPassed);
    	source.updateSortedFilteredItemPlace(theItem);
    }
    public String getTableTypeAndDefaultInfo()
    {
    	StringBuilder s=new StringBuilder();
    	s.append(this.tableName);s.append("(");
    	int n = columnDefaults.length;
    	for(int i=0;i<n;i++)
    	{
    		s.append(columnNames[i]);
    		s.append('[');
    		s.append(i);
    		s.append("]:");
    		Object a=columnDefaults[i];

    		if (a!=null)
    		{
        		s.append(a.toString());
        		s.append('/');
    			s.append(a.getClass().getCanonicalName());
    		} else s.append("null");
    		s.append(i==n-1?')':',');
    	}
    	n = columnTypes.length;
    	s.append('[');
    	for(int i=0;i<n;i++)
    	{
    		s.append(i);
    		s.append(':');
    		Class<?> a=columnTypes[i];
   			s.append(a.getCanonicalName());
    		s.append(i==n-1?']':',');
    	}
    	return s.toString();
    }
    public boolean setPropertiesOfItem(Object itemId,String columnName,Object value)
    {
		if (!sourceReady) makeSourceReady();
      	int colno = getColumnIndex(columnName);
    	if (colno<0 || itemId==null) return false;
    	Class<?> coltype = columnTypes[colno];
    	if (coltype!=null)
    	{
    	   if (coltype==String.class)
    	   {
    		   if (!(value instanceof String)) value = value.toString();
    	   }
    	   else
    	   if (value instanceof AERawItem)
    	   {
    		   long lvalue =((AERawItem)value).getIdKey();
    		   if (coltype==Long.class) value = new Long(lvalue);
    		   else if (coltype==Integer.class)
    		    value=new Integer((int)lvalue);
    	    } else if (value instanceof Integer)
    	    {
    	    	if (coltype==Long.class) value = new Long(((Integer)value).longValue());
    	    } else if (value instanceof Long)
    	    {
    	    	if (coltype==Integer.class) value = new Integer(((Long)value).intValue());
    	    } 
    	}
    	AERawItem item = (AERawItem)source.get(itemId);
    	if (item==null) return false;
    	boolean res=item.setPropertyOfItem(colno,value);
    	if (res) this.registerItemProperties(item);
    	return res;
    }
    public boolean makeDirty(Object objid)
    {
		if (!sourceReady) makeSourceReady();
    	int n=source.indexOf(objid);
    	if (n<0) return false;
    	((AERawItem)source.get(n)).makeDirty();
    	return true;
    }
    public void makeAllDirty()
    {
		if (!sourceReady) makeSourceReady();
    	int n = source.size();
    	for(int i=0;i<n;i++) ((AERawItem)source.get(i)).makeDirty();
    }
    public boolean refreshGeneral() throws Exception
    {
    	sourceReady = false;
    	return refresh();
    }
	public boolean refresh() throws Exception
	{
		if (!opened) return true;
		boolean anychange=false,wasSourceReady=true;
		if (source==null || !sourceReady) 
			{
			    source =new UniVector();
			    int n = editSource.size();
			    for(int i=0;i<n;i++) source.add(editSource.get(i));
			    wasSourceReady = false;
			}
	    sourceReady = true;
		if (parentColumn>=0 && columnDefaults[parentColumn]==null && !generalParentView)
		{
			if (source.size()==0) return false;
			source.clear();
			fireContentsChange();
            return true;				
		}
		//if (!modified)
		{
			List<Object> a;
			if (!generalParentView)
				a = engine.SelectRows(tableName_Alias,tableName,AppEngineRowLimit,AppEngineRowOffset,aeFilter,aeSort);
			else
			    a= engine.SelectRowsGeneral(tableName_Alias,tableName,AppEngineRowLimit,AppEngineRowOffset,aeFilter,aeSort);
			boolean filterNeeded = !engine.isAEFilterAndSorterSupported() && aeFilter!=null && aeFilter.size()>0; 
			int n = source.size();
			//System.out.println("table:"+tableName+" alias:"+tableName_Alias+" limit:"+AppEngineRowLimit+" rows:"+a.size()+" general:"+generalParentView);
			if (wasSourceReady)
			  for(int i=0;i<n;i++)
			  {
				AERawItem item = (AERawItem)source.get(i);
				if (!editSource.contains(item)) item.setNew(true);
			  }
			n = a.size();
			
			restrictEventFire = true;
			if (!generalParentView)
			 for(int i=0;i<n;i++)
			 {
				Object entity = a.get(i);
				long id =engine.vvdGetPrimaryId(entity,primaryKey,idColumn);
				if (id>0)
				{
					Object objectid;
					if (isPrimaryKeyLong)
						{
						  objectid = new Long(id);
						}
					else
					{
						objectid = new Integer((int)id);
					}
					if (delsource.contains(objectid)|| editSource.contains(objectid))
						continue;
					AERawItem item=(AERawItem)source.get(objectid);
					boolean isNewOne = true;
					if (item==null)
					{
						item = addCustomItem(objectid,false);
						item.copyFromEntity(entity,engine);
						modified=true;
					}
					else isNewOne=item.copyFromEntity(entity,engine);
					if (isNewOne)
					{
						registerItemProperties(item);
					}
					if (filterNeeded)
					{
						if (generalFilterOut(item))
						{
							source.remove(item);
							modified=true;
						}
					}
				}
			 }
			else
			{
				 int[] rowAdds=new int[]{parentColumn};
				 for(int i=0;i<n;i++)
				 {
					Object entity = a.get(i);
					long id = engine.vvdGetPrimaryId(entity,primaryKey,idColumn);
					if (id>=0)
					{
						Object objectid;
						if (isPrimaryKeyLong)
							{
							   objectid = new Long(id);
							}
						else
						{
						   objectid = new Integer((int)id);
						}
						if (delsource.contains(objectid)||editSource.contains(objectid))
							continue;
						columnDefaults[idColumn]=objectid;
				        AERawItem item = new AERawItem(this,columnNames,columnDefaults,columnTypes,false,primaryKey,generatedData);
						item.copyFromEntity(entity,engine);
                        item.addPrimaryKeyFields(rowAdds);
						AERawItem ditem=(AERawItem)source.get(item);
						boolean isNewOne = true;
						if (ditem!=null)
						{
							isNewOne = !item.isExactCopy(ditem);
						}
						if (isNewOne)
						{
							source.add(item);
							registerItemProperties(item);
							modified=true;
						}
						else ditem.setNew(false);
						if (filterNeeded)
						{
							if (generalFilterOut(item))
							{
								source.remove(item);
								modified=true;
							}
						}
					}
				 }
			}	
			
			n=source.size();
			int[] toremove = new int[n];
			int rmsize=0;
			for(int i=0;i<n;i++)
			{
				AERawItem item =(AERawItem) source.get(i);
				if (item.isNew() && !editSource.contains(item))
				{
					toremove[rmsize++]=i;
				}
			}
			if (rmsize>0)
			{
			   modified = true;
			   for(int i=0;i<rmsize;i++)
			   {
					source.remove(toremove[i]);
			   }
			}
			if (modified)
			{
				restrictEventFire=false;
				modified=delsource.size()>0 || editSource.size()>0;
				fireContentsChange();
				anychange=true;
			}
		}
		restrictEventFire=false;
		modified=delsource.size()>0 || editSource.size()>0;
		return anychange;
	}
	public void placeDistinctColumnValues(HashSet<Object> src,String column,Filter[] whereFilters)
	{
		if (!sourceReady) makeSourceReady();
		int n=source.getSortedFilteredSize();
		int colno = column==null?idColumn:this.getColumnIndex(column);
		if (colno<0) return;
		List<?> lst = source.getSortedFilteredSource();
		int nf = whereFilters==null?0:whereFilters.length;
	    for(int i=0;i<n;i++)
	    {
	    	AERawItem item =((AERawItem)lst.get(i)); 
	    	Object dat = item.getColumnValue(colno);
	    	boolean b=true;
	    	for(int j=0;j<nf;j++)
	    	{
	    		if (!whereFilters[j].passesFilter(item, item)) {b=false;break;}
	    	}
	    	if (!b) continue;
	    	src.add(dat);
	    }
	}
	public void placeDistinctColumnValuesUnfiltered(HashSet<Object> src,String column,Filter[] whereFilters)
	{
		if (!sourceReady) makeSourceReady();
		int n=source.size();
		int colno = column==null?idColumn:this.getColumnIndex(column);
		if (colno<0) return;
		List<?> lst = source;
		int nf = whereFilters==null?0:whereFilters.length;
	    for(int i=0;i<n;i++)
	    {
	    	AERawItem item =((AERawItem)lst.get(i)); 
	    	Object dat = item.getColumnValue(colno);
	    	boolean b=true;
	    	for(int j=0;j<nf;j++)
	    	{
	    		if (!whereFilters[j].passesFilter(item, item)) {b=false;break;}
	    	}
	    	if (!b) continue;
	    	src.add(dat);
	    }
	}
	public HashSet<?> getDistinctColumnValues(String column,Filter[] whereFilters)
	{
		HashSet<Object> src = new HashSet<Object>();
		placeDistinctColumnValues(src,column,whereFilters);
	    return src;
	}
	public HashSet<?> getDistinctColumnValuesUnfiltered(String column,Filter[] whereFilters)
	{
		HashSet<Object> src = new HashSet<Object>();
		placeDistinctColumnValuesUnfiltered(src,column,whereFilters);
	    return src;
	}
	public HashSet<?> getDistinctColumnValues(String column)
	{
		return getDistinctColumnValues(column,null);
	}
	public HashSet<?> getDistinctColumnValuesUnfiltered(String column)
	{
		return getDistinctColumnValuesUnfiltered(column,null);
	}
	public void placeDistinctColumnValuesUnfiltered(HashSet<Object> src,String receivedColumn,String searchColumn,Object value)
	{
		if (!sourceReady) makeSourceReady();
		int n=source.size();
		int colnoReceived = receivedColumn==null?idColumn:this.getColumnIndex(receivedColumn);
		int colnoSearch = searchColumn==null?idColumn:this.getColumnIndex(searchColumn);
		if (colnoReceived<0 || colnoSearch<0) return;
		List<?> lst = source;
	    for(int i=0;i<n;i++)
	    {
	    	AERawItem item =((AERawItem)lst.get(i)); 
	    	Object dat = item.getColumnValue(colnoReceived);
	    	Object srch = item.getColumnValue(colnoSearch);
	    	if (srch==null)
	    	{
	    		if (value!=null) continue;
	    	}
	    	else if (value==null) continue;
	    	else	    	
	    	{
	    		if (!srch.equals(value)) continue;
	    	}
	    	src.add(dat);
	    }
	}
	public void placeDistinctColumnValues(HashSet<Object> src,String receivedColumn,String searchColumn,Object value)
	{
		if (!sourceReady) makeSourceReady();
		int n=source.getSortedFilteredSize();
		int colnoReceived = receivedColumn==null?idColumn:this.getColumnIndex(receivedColumn);
		int colnoSearch = searchColumn==null?idColumn:this.getColumnIndex(searchColumn);
		if (colnoReceived<0 || colnoSearch<0) return;
		List<?> lst = source.getSortedFilteredSource();
	    for(int i=0;i<n;i++)
	    {
	    	AERawItem item =((AERawItem)lst.get(i)); 
	    	Object dat = item.getColumnValue(colnoReceived);
	    	Object srch = item.getColumnValue(colnoSearch);
	    	if (srch==null)
	    	{
	    		if (value!=null) continue;
	    	}
	    	else if (value==null) continue;
	    	else	    	
	    	{
	    		if (!srch.equals(value)) continue;
	    	}
	    	src.add(dat);
	    }
	}
	public HashSet<?> getDistinctColumnValues(String receivedColumn,String searchColumn,Object id)
	{
		HashSet<Object> src = new HashSet<Object>();
		placeDistinctColumnValues(src,receivedColumn,searchColumn,id);
	    return src;
	}
	public HashSet<?> getDistinctColumnValuesUnfiltered(String receivedColumn,String searchColumn,Object id)
	{
		HashSet<Object> src = new HashSet<Object>();
		placeDistinctColumnValuesUnfiltered(src,receivedColumn,searchColumn,id);
	    return src;
	}

	@Override
    public String toString()
    {
    	return tableName;
    }
	protected void preCommitValidated() throws Exception
	{
		
	}
    public void commit() throws Exception
    {
    	//System.out.println("AETableContainer.commit is called");
    	preCommitValidated();
    	validate();
    	commitTransaction(null);
    }
    public void commitTransaction(AETransactionData transdata) throws Exception
    {
    	if (transdata==null) transdata=new AETransactionData();
    	if (modified)
    	{
    		int n = delsource.size();
    		if (n>0)
    		{
    			for(int i=0;i<n;i++)
    			{
    				   AERawItem item =(AERawItem)delsource.get(i);
    				   engine.vDeleteRowById(tableName_Alias,tableName,item.getIdKey(),transdata,false,primaryKey);
    			}
    			delsource.clear();
    		}
    		n=editSource.size();
    		
    		for(int i=0;i<n;i++)
    		{
    			AERawItem item = (AERawItem)editSource.get(i);
    			//System.out.println("Added "+item.getDetailedInfo(AERawItem.SHOW_INFO_WITH_TYPES));
    			if (item.isModified())
    			{
    				if (!isValidItem(item)) throw new AEInvalidItemException("Invalid item: "+item);
    				boolean needUpdate=false;
    				if (item.getIdKey()<=0) needUpdate=true;
    				engine.InsertUpdateRow(tableName_Alias,tableName, item,transdata,false);
    				if (needUpdate && sourceReady && source!=null) source.notifyOfKeyChange(i);
    				registerItemProperties(item);
    			}
    		}
    		modified = false;
    		editSource.clear();
    		if (commitDependant!=null)
    		{
    			try
    			{
    			      //commitDependant.commitTransaction(transdata);
    				  commitDependant.commit();
    			      engine.FinalTransactionCommit(transdata);
    			}
    			catch(Exception e)
    			{
    				try
    				{
    					engine.FinalTransactionRollback(transdata);
    				}
    				catch(Exception e1)
    				{
    					
    				}
    				throw e;
    			}
    			commitDependant=null;
    		}
    		else
    		{
    			engine.FinalTransactionCommit(transdata);
    		}
    		if (engine.isCommitNeededRefresh()) refresh();
    		else fireContentsChange();
    	}
    	refresh();
    }
    public void rollback() throws Exception
    {
		   commitDependant=null;
    	   editSource.clear();
		   if (source==null || !sourceReady)
		   {
			   delsource.clear();
			   modified=false;
			   refresh();
			   return;
		   }
   	if (modified)
    	{
    	  int n=delsource.size();	
    	  if (n>0)
    	  {
    		for(int i=0;i<n;i++)
    		{
    			AERawItem item = (AERawItem)delsource.get(i);
    			source.add(item);
    			registerItemProperties(item);
    		}
    		delsource.clear();
    	  }
    	  n=source.size();
    	  int rmsize=0,rmstart=0;
    	  for(int i=0;i<n;i++)
    	  {
    		  AERawItem item =(AERawItem)source.get(i);
    		  if (item.isNew()) {if (rmsize==0) rmstart=i; rmsize++;}
    		  else if (item.isModified()) 
    			  {
    			     item.rollback();
    			     registerItemProperties(item);
    			  }
    	  }
    	  if (rmsize>0)
    	  {
    		  int[] toremove = new int[rmsize];
    		  int rmno=0;
    	      for(int i=rmstart;(i<n)&&(rmno<rmsize);i++)
    	      {
        		  AERawItem item =(AERawItem)source.get(i);
        		  if (item.isNew()) toremove[rmno++]=i;
    	      }
    	      for(rmno=0;rmno<rmsize;rmno++)
    	      {
    	    	  source.remove(toremove[rmno]);
    	      }
    	  }
    	}  
    	modified=false;
    	if (!refresh()) fireContentsChange();
    }
    public void setTableParentColumn(String columnName,Object data) throws Exception
    {
    	Integer IParentColumn = colNameMap.get(columnName);
    	if (IParentColumn==null)
    		throw new Exception("Bad parent column name");
    	parentColumn = IParentColumn.intValue();
    	setTableParentNewValue(data,false);
    }
    public void setTableParentNewValue(Object data,boolean refreshImmediately) throws Exception
    {
    	if (data!=null)
    	{
    		generalParentView=false;
    		Class<?> coltype = columnTypes[parentColumn];
    	   if (data instanceof AERawItem)
    		   {
    		      long lvalue = ((AERawItem)data).getIdKey();
    		      if (coltype==Integer.class)
    		           data = new Integer((int)lvalue);
    		      else if (coltype==Long.class)
    		    	  data = new Long(lvalue);
    		      else if (coltype==String.class)
    		    	  data = Long.toString(lvalue);
    		   }
    	   Object prevData = columnDefaults[parentColumn];
    	   columnDefaults[parentColumn]=data;
    	   tableName_Alias = tableName+"_"+data;
    	   if (prevData!=null && prevData.equals(data))
    	   {
    	        if (refreshImmediately) refresh();    	
                return;    		   
    	   }
    	}
    	else 
    	{
    		   generalParentView=true;
    		   tableName_Alias = tableName+"_";
    	}
    	modified = false;
    	if (source!=null) source.clear();
    	if (!engine.isBaseTableSeparationSupported()) ensureParentColumnFilter();
        if (refreshImmediately)
        	{
        	   refresh();
        	   fireContentsChange();
        	}
    }
    public void setTableParentNewValue(Object data) throws Exception
    {
    	setTableParentNewValue(data,true);
    }
    public void addListener(Container.ItemSetChangeListener listener) {
        if (itemSetChangeListeners == null) {
            itemSetChangeListeners = new LinkedList<Container.ItemSetChangeListener>();
        }
        itemSetChangeListeners.add(listener);
    }


    public void removeListener(Container.ItemSetChangeListener listener) {
        if (itemSetChangeListeners != null) {
            itemSetChangeListeners.remove(listener);
        }
    }

    public void fireContentsChange() {
    	if (this.restrictEventFire)
    	{
    		restrictEventFireSuspended=true;
    		return;
    	}
    	else restrictEventFireSuspended=false;
    	//System.out.println("AETableContainer.fireContentsChange called");
    	//showSourceDetailedInfo(SHOW_INFO_FILTERED | SHOW_INFO_FULL_ROW);
    	//try {throw new Exception("fired");}	catch(Exception e){e.printStackTrace();	}
        if (itemSetChangeListeners != null) {
        	final Object[] l = itemSetChangeListeners.toArray();
            final Container.ItemSetChangeEvent event = new AETableContainer.ItemSetChangeEvent(
                    this);
            int k=-1;
            for (int i = 0; i < l.length; i++) {
            	Container.ItemSetChangeListener lr= (Container.ItemSetChangeListener) l[i];
                lr.containerItemSetChange(event);
                if (lr instanceof AEListenerClosable && k<0)
                {
                	if (((AEListenerClosable)lr).isClosed()) k=i;
                }

            }
            if (k>=0) itemSetChangeListeners.remove(k);
        }
    } 
    public class ItemSetChangeEvent extends EventObject implements
                Container.ItemSetChangeEvent {
      
		      private static final long serialVersionUID = 7163872980150721147L;
			private  ItemSetChangeEvent(AETableContainer source) {
                super(source);
            }
            public Container getContainer() {
                return (Container) getSource();
            }
        }
	@Override
	public Item getItem(Object itemId) {
		if (itemId==null) return null;
		if (!sourceReady) makeSourceReady();
		int n=source.getSortedFilteredIndex(itemId);
		if (n<0) return null;
		return (Item)source.getSortedFilteredSource().get(n);
	}
	@Override
	public Collection<?> getItemIds() {
		if (!sourceReady) makeSourceReady();
		ArrayList<?> src=source.getSortedFilteredSource();
		return src;
	}
	public void showSourceDetailedInfo(int options)
	{
		if (!sourceReady) makeSourceReady();
		int fullLen=source.size();
		int filteredLen=source.getSortedFilteredSize();
        System.out.println("Table: "+tableName+" full size: "+fullLen+" filtered size: "+filteredLen+" parent: "+(parentColumn<0?"No":columnDefaults[parentColumn]));
        if ((options & SHOW_INFO_FILTERS)!=0)
        {
        	int n = filters.size();
        	if (n==0) System.out.println("No filters");
        	for(int i=0;i<n;i++)
        	{
        		Filter filter = (Filter)filters.get(i);
        		System.out.println("Filter "+i+": "+filter);
        	}
        }
        if ((options & SHOW_INFO_INTERNAL)!=0)
		  source.listOut(System.out, 0);
        int rawoptions=(options & SHOW_INFO_WITH_TYPES)!=0? AERawItem.SHOW_INFO_WITH_TYPES:0;
        if ((options & SHOW_INFO_FILTERED)!=0)
        {
        	for(int i=0;i<filteredLen;i++)
        	{
        		AERawItem item =(AERawItem) getIdByIndex(i);
        		System.out.println(i+": "+item.getDetailedInfo(rawoptions));
        	}
        }
        if ((options & SHOW_INFO_FULL_ROW)!=0)
        {
        	for(int i=0;i<fullLen;i++)
        	{
        		AERawItem item =(AERawItem) source.get(i);
        		System.out.println(i+"- "+item.getDetailedInfo(rawoptions));
        	}
        }
        
	}
	@Override
	public int size() {
		if (!sourceReady) makeSourceReady();
		return source.getSortedFilteredSize();
	}
	@Override
	public boolean containsId(Object itemId) {
		if (!sourceReady) makeSourceReady();
		return source.getSortedFilteredIndex(itemId)>=0;
	}
	@Override
	public boolean removeItem(Object itemId)
			throws UnsupportedOperationException {
		if (!sourceReady) makeSourceReady();
		Object item =source.get(itemId);
		if (item==null) return false;
		source.remove(item);
		delsource.add(item);
		setModified(true);
		return true;
	}
	public boolean removeItemDirect(Object itemId)
	{
		if (!sourceReady) makeSourceReady();
		Object item =source.get(itemId);
		if (item==null) return false;
		source.remove(item);
		delsource.add(item);
		modified = true;
		return true;
	}
	
	@Override
	public boolean removeContainerProperty(Object propertyId)
			throws UnsupportedOperationException {
		throw new UnsupportedOperationException("AETableContainer cannot remove a property");
	}
	@Override
	public boolean removeAllItems() throws UnsupportedOperationException {
		if (!sourceReady) makeSourceReady();
        int n=source.size();
        if (n>0)
        {
        	for(int i=0;i<n;i++) delsource.add(source.get(i));
        	source.clear();
    		setModified(true);
        }
		return true;
	}
	@Override
	public Object nextItemId(Object itemId) {
        if (itemId==null) return null;
		if (!sourceReady) makeSourceReady();
        int n = source.getSortedFilteredIndex(itemId);
        if ((n<0)||((++n)>=source.getSortedFilteredSize())) return null;
        Object item = source.getSortedFilteredSource().get(n);
        //AEHelper.logInfo("nextItemId:"+n+"  of "+source.getSortedFilteredSize());
		return item;
	}
	@Override
	public Object prevItemId(Object itemId) {
        if (itemId==null) return null;
		if (!sourceReady) makeSourceReady();
		int n=source.getSortedFilteredIndex(itemId);
        if (n<=0) return null;
		return source.getSortedFilteredSource().get(n-1);
	}
	@Override
	public Object firstItemId() {
		if (!sourceReady) makeSourceReady();
		if (source.getSortedFilteredSize()==0) return null;
		return source.getSortedFilteredSource().get(0);
	}
	@Override
	public Object lastItemId() {
		if (!sourceReady) makeSourceReady();
		int n = source.getSortedFilteredSize();
		if (n==0) return null;
		return source.getSortedFilteredSource().get(n-1);
	}
	@Override
	public boolean isFirstId(Object itemId) {
		if (itemId==null) return false;
		if (!sourceReady) makeSourceReady();
		return (source.getSortedFilteredIndex(itemId)==0);
	}
	@Override
	public boolean isLastId(Object itemId) {
		if (!sourceReady) makeSourceReady();
		int n = source.getSortedFilteredSize();
		if (itemId==null || n==0) return false;
		return source.getSortedFilteredIndex(itemId)==n-1;
	}
	@Override
	public Object addItemAfter(Object previousItemId)
			throws UnsupportedOperationException {
		throw new UnsupportedOperationException("addItemAfter");
	}
	@Override
	public Item addItemAfter(Object previousItemId, Object newItemId)
			throws UnsupportedOperationException {
		throw new UnsupportedOperationException("addItemAfter");
	}
	protected void addContainerFilter_NoRefresh(Filter[] filter)
	{
		if (filter==null) return;
		int n=filter.length;
		for(int i=0;i<n;i++)
		{
			Filter filt=filter[i];
			if (filt==null) continue;
			filters.add(filt);
			if (filt instanceof AEExtendedFilter)
			{
				((AEExtendedFilter)filt).setParentContainer(this);
			}
		}
	}
	@Override
	public void addContainerFilter(Filter filter)
			throws UnsupportedFilterException {
		if (filter==null) return;
		filters.add(filter);
		if (filter instanceof AEExtendedFilter)
		{
			((AEExtendedFilter)filter).setParentContainer(this);
		}
		refilter_resorter();
	}
	
	public void addEqualContainerFilterColumnAndValue(String columnName,Object value)
	{
		long colno = this.getColumnIndex(columnName);
		if (colno<0)
			throw new RuntimeException("Equal filter "+columnName+" does not exist in container "+tableName);
		
		if (value!=null && value.getClass()!=columnTypes[(int)colno])
		{
			value = AEColumnProperty.conversion(columnTypes[(int)colno], value, false);
		}
		boolean isNew = multiEqualFilter==null;
		if (isNew)
		{
			multiEqualFilter = new AEMultiEqualFilter(multiEqualMap);
			filters.add(multiEqualFilter);
		}
		Object oldValue = multiEqualMap.getValueByKey(colno);
		if (oldValue!=value)
		{
			if (oldValue==null || value==null || !value.equals(oldValue))
			{
				isNew = true;
				multiEqualMap.addKeyAndValue(colno, value);
			}
		}
		if (isNew)
		{
			refilter_resorter();
		}
	}
	public void removeAllEqualContainerFilterColumns()
	{
		if (multiEqualFilter!=null)
		{
			removeContainerFilter(multiEqualFilter);
			multiEqualFilter = null;
			multiEqualMap.clear();
		}
	}
	public void removeEqualContainerFilterColumn(String columnName)
	{
		long colno = this.getColumnIndex(columnName);
		if (colno<0)
			throw new RuntimeException("column "+columnName+" supposed for equal filter removal does not exist in container "+tableName);
        int n = multiEqualMap.indexOf(new Long(colno));
        if (n<0) return;
        multiEqualMap.remove(n);
        if (multiEqualMap.size()==0) removeAllEqualContainerFilterColumns();
        else refilter_resorter();
	}
	public void fillDataWithEqualContainerFilterValues(Object[] data)
	{
		if (data==null) return;
		int n=multiEqualMap.size();
		int datalen = data.length;
		for(int i=0;i<n;i++)
		{
			LongElement elem = (LongElement)multiEqualMap.get(i);
			int colno =(int) elem.getLong();
			if (colno<datalen) data[colno]=elem.getValue();
		}
	}
	public void fillRawItemWithEqualContainerFilterValues(AERawItem item)
	{
		if (item==null) return;
		int n=multiEqualMap.size();
		for(int i=0;i<n;i++)
		{
			LongElement elem = (LongElement)multiEqualMap.get(i);
			int colno =(int) elem.getLong();
			item.setPropertyOfItem(colno, elem.getValue());
		}
	}
	public void refilter_resorter()
	{
		if (!sourceReady) makeSourceReady();
		if (filters.size()==0)
		{
			int n= source.size();
			for(int i=0;i<n;i++)
			{
				((AERawItem)source.get(i)).setFiltered(true);
			}
		}
		else
		{
			int n = source.size();
			for(int i=0;i<n;i++)
			{
				AERawItem a =(AERawItem)source.get(i); 
				boolean bFiltered = true;
				int nf = filters.size();
				for(int ft=0;(ft<nf)&& bFiltered;ft++)
				{
					Filter f = (Filter)filters.get(ft);
					if (!f.passesFilter(a, a)) bFiltered = false;
				}
				a.setFiltered(bFiltered);
			}
		}
		if ((filters.size()==0)&&(sortedColumnAmount==0))
		{
			source.clearSortingFiltering();
		}
		else source.reSortFilter();
		fireContentsChange();
	}
	public boolean applyTranslation(AETranslation translation)
	{
		if (translation==null) return false;
		boolean translated = false;
		int n = generatedData==null?0:generatedData.length;
		for(int i=0;i<n;i++)
		{
			if (generatedData[i] instanceof AEApplyTranslation)
			{
				((AEApplyTranslation)generatedData[i]).applyTranslation(translation);
				translated = true;
			}
		}
		if (translated)
		{
			int nn = delsource.size();
			for(int i=0;i<nn;i++)
			{
				AERawItem item = (AERawItem) delsource.get(i);
				item.updateGeneratedDataValues();
			}
			if (sourceReady)
			{
			   nn = source.size();
			   for(int i=0;i<nn;i++)
			   {
				AERawItem item = (AERawItem) source.get(i);
				item.updateGeneratedDataValues();
			   }
			}
		}
		return translated;
	}
	@Override
	public void removeContainerFilter(Filter filter) {
		if (filter==null) return;
		Filter oldFilter =(Filter)filters.get(filter);
		if (oldFilter==null) return;
		if (oldFilter instanceof AEExtendedFilter)
		{
			((AEExtendedFilter)oldFilter).onCloseEvent();
		}
		filters.remove(oldFilter);
	    refilter_resorter();
	}
	public void close()
	{
		this.opened = false;
		delsource.clear();
		editSource.clear();
		modified = false;
		sourceReady = false;
		source=null;
		removeAllContainerFilters();
	}
	public void cleanAndAddContainerFilter(Filter filter)
	{
		int n=filters.size();
        if (n>0)
        {
			multiEqualFilter = null;
			multiEqualMap.clear();
        	for(int i=0;i<n;i++)
        	{
        		Object o = filters.get(i);
        		if (o instanceof AEExtendedFilter)
        		{
        			((AEExtendedFilter)o).onCloseEvent();
        		}
        	}
        	filters.clear();
        }
		if (filter==null) return;
		filters.add(filter);
		if (filter instanceof AEExtendedFilter)
		{
			((AEExtendedFilter)filter).setParentContainer(this);
		}
		refilter_resorter();
	}
	@Override
	public void removeAllContainerFilters() {
		int n=filters.size();
        if (n>0)
        {
			multiEqualFilter = null;
			multiEqualMap.clear();
        	for(int i=0;i<n;i++)
        	{
        		Object o = filters.get(i);
        		if (o instanceof AEExtendedFilter)
        		{
        			((AEExtendedFilter)o).onCloseEvent();
        		}
        	}
        	filters.clear();
        	refilter_resorter();
        }
	}
	@Override
	public int indexOfId(Object itemId) {
		if (!sourceReady) makeSourceReady();
		return source.getSortedFilteredIndex(itemId);
	}
	@Override
	public Object getIdByIndex(int index) {
		if (!sourceReady) makeSourceReady();
		return source.getSortedFilteredSource().get(index);
	}
	@Override
	public Object addItemAt(int index) throws UnsupportedOperationException {
		throw new UnsupportedOperationException("addItemAt"); 
	}
	@Override
	public Item addItemAt(int index, Object newItemId)
			throws UnsupportedOperationException {
		throw new UnsupportedOperationException("addItemAt with object"); 
	}    
	/*****************************************************************
	 *  SORTING support
	 */
	protected boolean unsort_noRefresh()
	{
		if (sortedColumnAmount>0)
		{
			if (!sourceReady) makeSourceReady();
			sortedColumnAmount=0;
			if (filters.size()==0)
			{
				source.clearSortingFiltering();
			}
			else source.reSortFilter();
			return true;
		}
		return false;
		
	}
	public void unsort()
	{
		if (unsort_noRefresh())
		        fireContentsChange();
	}
	public int getColumnIndex(String column)
	{
		int n = columnNames.length;
		for(int i=0;i<n;i++) if (columnNames[i].equals(column)) return i;
		return -1;
	}
	private boolean internalAddSortingColumnAtEnd(String column,boolean ascending)
	{
		int colno = getColumnIndex(column);
		if (colno==-1) return false;
		int negcolno = -1 - colno;
		for(int i=0;i<sortedColumnAmount;i++)
		{
			if (sortedColumns[i]==colno || sortedColumns[i]==negcolno)
				return false;
		}
		sortedColumns[sortedColumnAmount++]=ascending?colno:negcolno;
		return true;
	}
	private boolean internalRemoveSortingColumn(int colno)
	{
		int negcolno = -1 - colno;
		int i=0,k=0;
		for(;i<sortedColumnAmount;i++)
		{
			int j =sortedColumns[i]; 
			if (j==colno || j==negcolno) continue;
			if (i==k) k++;
			else sortedColumns[k++]=j;
		}
		if (i!=k)
		{
			sortedColumnAmount = k;
			return true;
		}
		return false;
	}
	private boolean internalAddSortingColumnAtBeginning(String column,boolean ascending)
	{
		int colno = getColumnIndex(column);
		if (colno==-1) return false;
		int negcolno = -1 - colno;
		int realcolno = ascending?colno:negcolno;
		if (sortedColumnAmount==0)
		{
			sortedColumns[sortedColumnAmount++]=realcolno;
			return true;
		}
		if (sortedColumns[0]==colno || sortedColumns[0]==negcolno)
		{
			if (sortedColumns[0]!=realcolno)
			{
				sortedColumns[0]=realcolno;
				return true;
			}
			return false;
		}
		internalRemoveSortingColumn(colno);
		int m = sortedColumnAmount++;
		while (m>0)
		{
			sortedColumns[m]=sortedColumns[m-1];
			m--;
		}
		sortedColumns[0]=realcolno;
		return true;
	}
	public String getFirstSortColumn()
	{
		if (sortedColumnAmount==0 || sortedColumns==null || sortedColumns.length==0) return null;
		int colno = sortedColumns[0];
		if (colno<0) colno = -1 - colno;
		return columnNames[colno];
	}
	public boolean isFirstSortColumnAscending()
	{
		if (sortedColumnAmount==0 || sortedColumns==null || sortedColumns.length==0) return true;
		int colno = sortedColumns[0];
        return colno>=0;		
	}
	protected boolean sort_noRefresh(String[] columns,boolean[] ascending)
	{
		int n = columns==null?0:columns.length;
		if (n==0) { return unsort_noRefresh();}
		int na = ascending==null?0:ascending.length;
		int[] checksort = new int[sortedColumnAmount];
		for(int i=0;i<sortedColumnAmount;i++) checksort[i]=sortedColumns[i];
		sortedColumnAmount = 0;
		for(int i=0;i<n;i++)
		{
			internalAddSortingColumnAtEnd(columns[i],i<na?ascending[i]:true);
		}
		boolean bChange = (checksort.length!=sortedColumnAmount);
		if (!bChange)
		{
			for(int i=0;i<sortedColumnAmount;i++) 
				if (checksort[i]!=sortedColumns[i]) {bChange=true;break;}
		}
        return bChange;		
	}
    public void markColumnAsSortable(String columnName)
    {
    	if (columnNameCollection.contains(columnName))
    		sortedColumnNameCollection.add(columnName);
    }
    public void markColumnAsUnsortable(String columnName)
    {
    	sortedColumnNameCollection.remove(columnName);
    }
	@Override
	public Collection<?> getSortableContainerPropertyIds() {
		return sortedColumnNameCollection;
	}
    @Override
	public void sort(Object[] propertyId, boolean[] ascending)
	{
		String[] columns=null;
		if (propertyId!=null && propertyId.length>0)
		{
			int colAmnt = propertyId.length;
			columns = new String[colAmnt];
			for(int i=0;i<colAmnt;i++)
			{
				columns[i]=propertyId[i]==null?null:propertyId[i].toString();
			}
			if (colAmnt==1 && ascending!=null && ascending.length==1)
			{
				addSortingColumnAtBeginning(columns[0],ascending[0]);
				return;
			}
		}
		sortFixed(columns,ascending);
	}
    public void sortFixed(String[] columns,boolean[] ascending)
    {
		if (!sourceReady) makeSourceReady();
		if (sort_noRefresh(columns,ascending))
		{
			if (sortedColumnAmount>0) source.reSortFilter();
			fireContentsChange();
		}
    }
	protected void resortFilterCheck()
	{
		if (!sourceReady) makeSourceReady();
		if ((filters.size()>0)||(sortedColumnAmount>0)) source.reSortFilter();
	}
	public boolean addSortingColumnAtBeginning(String column,boolean ascending)
	{
		if (internalAddSortingColumnAtBeginning(column,ascending))
		{
			if (!sourceReady) makeSourceReady();
			source.reSortFilter();
			fireContentsChange();
			return true;
		}
		return false;
	}
	public boolean addSortingColumnAtEnd(String column,boolean ascending)
	{
		if (internalAddSortingColumnAtEnd(column,ascending))
		{
			if (!sourceReady) makeSourceReady();
			source.reSortFilter();
			fireContentsChange();
			return true;
		}
		return false;
	}
    public boolean removeSortingColumn(String column)
    {
    	int colno = getColumnIndex(column);
    	if (colno<0) return false;
    	if (internalRemoveSortingColumn(colno))
    	{
    		if (!sourceReady) makeSourceReady();
			source.reSortFilter();
			fireContentsChange();
			return true;
    	}
    	return false;
    }
    public boolean changeColumnSortingDirection(String column)
    {
    	int colno = getColumnIndex(column);
    	if (colno<0) return false;
        int negcolno = -1 - colno;
		if (!sourceReady) makeSourceReady();
        for(int i=0;i<sortedColumnAmount;i++)
        {
        	if (sortedColumns[i]==colno || sortedColumns[i]==negcolno)
        	{
        		sortedColumns[i]=-1-sortedColumns[i];
    			source.reSortFilter();
    			fireContentsChange();
        		return true;
        	}
        }
        return false;
    }
    public boolean addAscendingSortingColumnAtBeginningOrChangeDirection(String column)
    {
    	if (addSortingColumnAtBeginning(column,true))
    		return true;
    	return changeColumnSortingDirection(column);
    }
    public boolean isItemNew(int index)
    {
		if (!sourceReady) makeSourceReady();
    	AERawItem item = (AERawItem)source.getSortedFilteredSource().get(index);
    	return item==null || item.isNew();
    }
    protected boolean isValidItem(AERawItem item)
    {
    	int n=validators.size();
    	for(int i=0;i<n;i++)
    	{
    		if (!validators.get(i).isValid(this, item)) return false;
    	}
    	return true;
    }
    public void removeAllValidators()
    {
    	validators.clear();
    }
    public void addValidator(AEValidator validator)
    {
    	validators.add(validator);
    }
    public void removeAllForeignKeyConstraints()
    {
    	deleteConstraints.clear();
    }
    public void removeForeignKeyConstraint(AEForeignKeyConstraint foreignKeyConstraint)
    {
    	deleteConstraints.remove(foreignKeyConstraint);
    }
    public void addForeignKeyConstraint(AEForeignKeyConstraint foreignKeyConstraint)
    {
    	deleteConstraints.add(foreignKeyConstraint);
    }
    public void addForeignKeyConstraint(AETableContainer foreignContainer,String foreignKeyColumn)
    {
    	addForeignKeyConstraint(new AEForeignKeyConstraint(foreignContainer,foreignKeyColumn));
    }
	public void	validateDeleteConstraints() throws Exception
	{
		int ndeleted = delsource.size();
		if (ndeleted==0) return;
		int ndelcons = deleteConstraints.size();
		if (ndelcons==0) return;
		for(int i=0;i<ndeleted;i++)
		{
			AERawItem item =(AERawItem)delsource.get(i);
			for(int j=0;j<ndelcons;j++)
			{
				AEValidator aevalid=(AEValidator)deleteConstraints.get(j);
				if (!aevalid.isValid(this, item))
				{
					throw new AEForeignKeyException("Before removing items in table "+tableName+
							" you must remove referencing items in table "+aevalid.toString());
				}
			}
		}
	}

    //AppEngine specific functions
    public void AppEngineSetRowOffset(int offset)
    {
    	this.AppEngineRowOffset = offset;
    }
    public void AppEngineSetRowLimit(int limit)
    {
    	this.AppEngineRowLimit = limit;
    }
    /*********************
     * 
     * @param columnName is a column name
     * @param filterOperator is an operator and depends on the real engine.
     *   for AppEngine it must have the type of com.google.appengine.api.datastore.Query.FilterOperator 
     * @param value is a value to compare
     */
    public void AppEngineAddFilter(String columnName,AEFilter.FilterOperator filterOperator,Object value)
    {
    	int columnNo = getColumnIndex(columnName);
    	AEFilter filt = new AEFilter(columnName,filterOperator,value,columnNo<0?null:columnTypes[columnNo],columnNo);
    	aeFilter.add(filt);
    }
    public void AppEngineClearAllFilters()
    {
    	aeFilter.clear();
    	if (!engine.isBaseTableSeparationSupported() && parentColumn>=0)
    	{
    		ensureParentColumnFilter();
    	}
    }
    private boolean generalFilterOut(AERawItem item)
    {
    	int n=aeFilter==null?0:aeFilter.size();
    	for(int i=0;i<n;i++)
    	{
    		AEFilter filter = aeFilter.get(i);
    		if (item.filterOut(filter)) return true;
    	}
    	return false;
    }
    public void ensureParentColumnFilter()
    {
    	int n=aeFilter.size();
    	for(int i=0;i<n;i++)
    	{
    		AEFilter filter = aeFilter.get(i);
    		if (filter.isParentColumnUsed())
    		{
    			if (generalParentView) 
    			{
    				aeFilter.remove(i);
    				return;
    			}
    			Object val = columnDefaults[parentColumn];
    			filter.setValue(val);
    			return;
    		}
    	}
    	if (generalParentView) return;
		AEFilter filter = new AEFilter(columnNames[parentColumn],AEFilter.FilterOperator.EQUAL,columnDefaults[parentColumn],
				         columnTypes[parentColumn],parentColumn);
		filter.setParentColumnUsed();
		aeFilter.add(filter);
    }
    /******************************
     * 
     * @param columnName
     * @param sortDirection is a sorting direction (ascending or descending).
     *   for AppEngine it must have the type of com.google.appengine.api.datastore.Query.SortDirection
     */
    public void AppEngineAddSort(String columnName,AESort.SortDirection sortDirection)
    {
    	AESort sorter = new AESort(columnName,sortDirection);
    	aeSort.add(sorter);
    }
    public void AppEngineClearAllSorts()
    {
    	aeSort.clear();
    }
    public void setCommitDependant(AETableContainer container)
    {
    	commitDependant=container;    	
    }
    public void itemNotifiesOfModification(AERawItem item)
    {
    	editSource.add(item);
    	setModified(true);
    }
    public void releaseMemoryInIdleState()
    {
    	source = null;
    	sourceReady=false;
    }
}
